package com.ebo.synframework.nettybase.dispatcher;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

import com.google.protobuf.GeneratedMessageV3;
import net.sf.cglib.reflect.FastClass;
import net.sf.cglib.reflect.FastMethod;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import com.ebo.synframework.nettybase.protoPool.ReusedProtoBuilder;
import com.ebo.synframework.synroom.executor.SynRoomPlayer;
import com.google.protobuf.Descriptors.FieldDescriptor;
import com.google.protobuf.GeneratedMessage;

@Component
public class CmdDispatcher implements ApplicationContextAware{
	
	private ApplicationContext ac;
	
	private final ConcurrentHashMap<String, MethodInvoker> methodInvokers = new ConcurrentHashMap<String, MethodInvoker>(1000);
	
	@Override
	public void setApplicationContext(ApplicationContext ac) throws BeansException {
//		System.out.println("##cmddiapacher "+ac.toString());
        this.ac = ac;
    }
	
	public Object dispatch(ReqCmd reqCmd, SynRoomPlayer player) throws Throwable {
		ReusedProtoBuilder reusedProtoBuilder = null;
		try {
			GeneratedMessageV3 proto = reqCmd.getMessage();
			if(proto==null){
				reusedProtoBuilder = reqCmd.getReusedMessage();
				if(reusedProtoBuilder!=null){
					proto = (GeneratedMessageV3) reqCmd.getReusedMessage().getBuilder().build();
				}else{
					throw new RuntimeException("all message of reqCmd is null");
				}
			}
			String serviceAndMethodStr = reqCmd.getServiceAndMethodStr();
	        List<FieldDescriptor> fields = proto.getDescriptorForType().getFields();
	        Object[] params = getSortedParams(fields, proto, player); //把player放入params作为最后一个参数
	        MethodInvoker methodInvoker = methodInvokers.get(serviceAndMethodStr);
	        if(methodInvoker == null) {
	            int pointIndex = 0;
	            for(int i = 0;i<serviceAndMethodStr.length();i++) {
	                if(serviceAndMethodStr.charAt(i) == '.') {
	                    pointIndex = i;
	                    break;
	                }
	            }
	            String sericeName = serviceAndMethodStr.substring(0, pointIndex);
	            String methodName = serviceAndMethodStr.substring(pointIndex + 1);
	            Object service = ac.getBean(sericeName);
	            FastMethod fastMethod = findFastMethod(service, sericeName, methodName, params);
	            if(fastMethod == null) {
	                throw new RuntimeException("can't find the method:" + serviceAndMethodStr + " with params'type");
	            }
	            methodInvoker = new MethodInvoker(fastMethod, service);
	            methodInvokers.put(serviceAndMethodStr, methodInvoker);
	        }
	        return methodInvoker.invoke(params);
		} catch (InvocationTargetException e) {
			throw e.getTargetException();
		} finally{
			if(reusedProtoBuilder!=null){
				reusedProtoBuilder.release();
			}
		}
		
		
	}
	
	public Object dispatch(long roleId, String serviceAndMethodStr, GeneratedMessage proto, SynRoomPlayer player) throws Throwable {
		try {
	        List<FieldDescriptor> fields = proto.getDescriptorForType().getFields();
	        Object[] params = getSortedParams(fields, proto, roleId, player); //把player放入params作为最后一个参数
	        MethodInvoker methodInvoker = methodInvokers.get(serviceAndMethodStr);
	        if(methodInvoker == null) {
	            int pointIndex = 0;
	            for(int i = 0;i<serviceAndMethodStr.length();i++) {
	                if(serviceAndMethodStr.charAt(i) == '.') {
	                    pointIndex = i;
	                    break;
	                }
	            }
	            String sericeName = serviceAndMethodStr.substring(0, pointIndex);
	            String methodName = serviceAndMethodStr.substring(pointIndex + 1);
	            Object service = ac.getBean(sericeName);
	            FastMethod fastMethod = findFastMethod(service, sericeName, methodName, params);
	            if(fastMethod == null) {
	                throw new RuntimeException("can't find the method:" + serviceAndMethodStr + " with params'type");
	            }
	            methodInvoker = new MethodInvoker(fastMethod, service);
	            methodInvokers.put(serviceAndMethodStr, methodInvoker);
	        }
	        return methodInvoker.invoke(params);
		} catch (InvocationTargetException e) {
			throw e.getTargetException();
		}
		
		
	}

	private Object[] getSortedParams(List<FieldDescriptor> fields, GeneratedMessageV3 proto, SynRoomPlayer player) {
        int size = fields.size();
        Object[] params = new Object[size + 1];
        for (int i = 0; i < size; i++) {
            FieldDescriptor fieldDescriptor = fields.get(i);
            params[i] = proto.getField(fieldDescriptor);
        }
        params[size] = player;
        return params;
    }
	
	private Object[] getSortedParams(List<FieldDescriptor> fields, GeneratedMessage proto, long roleId, SynRoomPlayer player) {
        int size = fields.size();
        Object[] params = new Object[size + 2];
        for (int i = 0; i < size; i++) {
            FieldDescriptor fieldDescriptor = fields.get(i);
            params[i] = proto.getField(fieldDescriptor);
        }
        params[size] = roleId;
        params[size+1] = player;
        return params;
    }
	
	private FastMethod findFastMethod(Object service, String serviceName, String methodName, Object[] params) {
        for (Method method : service.getClass().getMethods()) {
            if (method.getName().equals(methodName)) {
                if(method.getParameterTypes().length != params.length) {
                    continue;
                    //throw new RuntimeException("the param's length it not fix the method:" + serviceName + "." + methodName);
                }
                FastClass fastClass = FastClass.create(service.getClass());
                FastMethod fastMethod = fastClass.getMethod(methodName, method.getParameterTypes());
                return fastMethod;
            }
        }
        throw new RuntimeException("can not find the fast method:" + serviceName + "." + methodName);
    }
	
}
